/*
 * Copyright 2007-2008 Andrew O'Malley
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package ws.quokka.plugin.maven;

import ws.quokka.core.bootstrap_util.Assert;
import ws.quokka.core.repo_spi.RepoArtifactId;
import ws.quokka.core.util.xml.Document;
import ws.quokka.core.util.xml.Element;
import ws.quokka.core.version.Version;

import java.io.File;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;


/**
 *
 */
public class IdMapper {
    //~ Static fields/initializers -------------------------------------------------------------------------------------

    private static final String ID_MAPPINGS = "id-mappings";
    private static final String ID_MAPPING = "id-mapping";
    private static final String FROM = "from";
    private static final String TO = "to";
    private static final String TYPE_MAPPING = "type-mapping";
    private static final String TYPE_MAPPINGS = "type-mappings";
    private static final String LICENSE_MAPPING = "license-mapping";
    private static final String LICENSE_MAPPINGS = "license-mappings";
    private static final String TO_GROUP = "to-group";
    private static final String TO_VERSION = "to-version";

    //~ Instance fields ------------------------------------------------------------------------------------------------

    private Map idMappings = new HashMap();
    private Map typeMappings = new HashMap();
    private Map licenseMappings = new HashMap();

    //~ Methods --------------------------------------------------------------------------------------------------------

    public void addIdMapping(IdMapping mapping) {
        idMappings.put(mapping.getMavenId(), mapping);
    }

    public void addTypeMapping(TypeMapping mapping) {
        typeMappings.put(mapping.getMavenType(), mapping);
    }

    public LicenseMapping getLicenseMapping(String licenseName) {
        return (licenseName == null) ? null : (LicenseMapping)licenseMappings.get(licenseName);
    }

    public RepoArtifactId toId(MavenId id) {
        IdMapping mapping = getIdMapping(id);

        return (mapping == null) ? null
                                 : mapping.toId(id).merge(new RepoArtifactId(null, null, toType(id.getType()),
                (Version)null));
    }

    private String toType(String type) {
        TypeMapping mapping = (TypeMapping)typeMappings.get(type);

        return (mapping == null) ? type : mapping.getQuokkaType();
    }

    public IdMapping getIdMapping(MavenId id) {
        // Try to get the most specific match first
        List keys = new ArrayList();
        keys.add(id);
        keys.add(new MavenId(id.getGroupId(), id.getArtifactId(), null, id.getClassifier(), id.getVersion()));
        keys.add(new MavenId(id.getGroupId(), id.getArtifactId(), null, id.getClassifier(), null));
        keys.add(new MavenId(id.getGroupId(), id.getArtifactId(), null, null, null));

        for (Iterator i = keys.iterator(); i.hasNext();) {
            MavenId mavenId = (MavenId)i.next();
            IdMapping mapping = (IdMapping)idMappings.get(mavenId);

            if (mapping != null) {
                return mapping;
            }
        }

        return null;
    }

    public Collection getIdMappings() {
        return idMappings.values();
    }

    public Collection getTypeMappings() {
        return typeMappings.values();
    }

    public void merge(IdMapper mapper) {
        for (Iterator i = mapper.getIdMappings().iterator(); i.hasNext();) {
            IdMapping mapping = (IdMapping)i.next();
            addIdMapping(mapping);
        }

        for (Iterator i = mapper.getTypeMappings().iterator(); i.hasNext();) {
            TypeMapping mapping = (TypeMapping)i.next();
            addTypeMapping(mapping);
        }

        for (Iterator i = mapper.getLicenseMappings().iterator(); i.hasNext();) {
            LicenseMapping mapping = (LicenseMapping)i.next();
            addLicenseMapping(mapping);
        }
    }

    public static IdMapper load(File file) {
        return load(Document.parse(file));
    }

    private static IdMapper load(Document document) {
        IdMapper mapper = new IdMapper();
        Element idMappingsEl = document.getRoot().getChild(ID_MAPPINGS);

        if (idMappingsEl != null) {
            for (Iterator i = idMappingsEl.getChildren(ID_MAPPING).iterator(); i.hasNext();) {
                Element idMappingEl = (Element)i.next();
                mapper.addIdMapping(new IdMapping(idMappingEl.getAttribute(FROM), idMappingEl.getAttribute(TO)));
            }
        }

        Element typeMappingsEl = document.getRoot().getChild(TYPE_MAPPINGS);

        if (typeMappingsEl != null) {
            for (Iterator i = typeMappingsEl.getChildren(TYPE_MAPPING).iterator(); i.hasNext();) {
                Element typeMappingEl = (Element)i.next();
                mapper.addTypeMapping(new TypeMapping(typeMappingEl.getAttribute(FROM), typeMappingEl.getAttribute(TO)));
            }
        }

        Element licenseMappingsEl = document.getRoot().getChild(LICENSE_MAPPINGS);

        if (licenseMappingsEl != null) {
            for (Iterator i = licenseMappingsEl.getChildren(LICENSE_MAPPING).iterator(); i.hasNext();) {
                Element licenseMappingEl = (Element)i.next();
                String name = licenseMappingEl.getAttribute(FROM);
                String group = licenseMappingEl.getAttribute(TO_GROUP);
                String version = licenseMappingEl.getAttribute(TO_VERSION);
                boolean manual = "true".equals(licenseMappingEl.getAttribute("manual"));
                Assert.isTrue((manual && (group == null) && (version == null))
                    || (!manual && (group != null) && (name != null)),
                    "Either manual must equal 'true' or to-group and to-version must be specified");

                LicenseMapping mapping = new LicenseMapping(name, manual);

                if (!manual) {
                    RepoArtifactId id = new RepoArtifactId(group, null, "license", Version.parse(version))
                        .mergeDefaults();
                    mapping = new LicenseMapping(name, id);
                }

                mapper.addLicenseMapping(mapping);
            }
        }

        return mapper;
    }

    public Collection getLicenseMappings() {
        return licenseMappings.values();
    }

    public void save(File file) {
        Document document = Document.create();
        Element rootEl = document.addRootElement("mappings");

        Element idMappingsEl = rootEl.addChild(ID_MAPPINGS);

        for (Iterator i = idMappings.values().iterator(); i.hasNext();) {
            IdMapping mapping = (IdMapping)i.next();
            idMappingsEl.addChild(ID_MAPPING).setAttribute(FROM, stripTrailingColons(mapping.getMavenId().toString()))
            .setAttribute(TO, stripTrailingColons(mapping.getId().toShortString()));
        }

        Element typeMappingsEl = rootEl.addChild(TYPE_MAPPINGS);

        for (Iterator i = typeMappings.values().iterator(); i.hasNext();) {
            TypeMapping mapping = (TypeMapping)i.next();
            typeMappingsEl.addChild(TYPE_MAPPING).setAttribute(FROM, mapping.getMavenType()).setAttribute(TO,
                mapping.getQuokkaType());
        }

        Element licenseMappingsEl = rootEl.addChild(LICENSE_MAPPINGS);

        for (Iterator i = licenseMappings.values().iterator(); i.hasNext();) {
            LicenseMapping mapping = (LicenseMapping)i.next();
            licenseMappingsEl.addChild(LICENSE_MAPPING).setAttribute(FROM, mapping.getName())
            .setAttribute(TO_GROUP, mapping.getId().getGroup()).setAttribute(TO_VERSION,
                mapping.getId().getVersion().toString());
        }

        document.toXML(file);
    }

    private String stripTrailingColons(String string) {
        int i;

        for (i = string.length() - 1; (i >= 0) && (string.charAt(i) == ':'); i--) {
            ;
        }

        return string.substring(0, i + 1);
    }

    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if ((o == null) || (getClass() != o.getClass())) {
            return false;
        }

        IdMapper idMapper = (IdMapper)o;

        if ((idMappings != null) ? (!idMappings.equals(idMapper.idMappings)) : (idMapper.idMappings != null)) {
            return false;
        }

        if ((typeMappings != null) ? (!typeMappings.equals(idMapper.typeMappings)) : (idMapper.typeMappings != null)) {
            return false;
        }

        return true;
    }

    public int hashCode() {
        int result;
        result = ((idMappings != null) ? idMappings.hashCode() : 0);
        result = (31 * result) + ((typeMappings != null) ? typeMappings.hashCode() : 0);

        return result;
    }

    public void addLicenseMapping(LicenseMapping licenseMapping) {
        licenseMappings.put(licenseMapping.getName(), licenseMapping);
    }
}
